// Copyright (c) 2025 刻BITTER
//
// This Source Code Form is subject to the terms of the Mozilla Public
// License, v. 2.0. If a copy of the MPL was not distributed with this
// file, You can obtain one at https://mozilla.org/MPL/2.0/.


#pragma once


#include <cstdint>


namespace button_trigger {

    union EventFlag {
        using FlagType = uint_fast8_t;

        FlagType flag;

        struct {
            uint8_t pressed : 1;
            uint8_t released : 1;
            uint8_t clicked : 1;
            uint8_t long_pressed : 1;
            uint8_t long_press_released : 1;
        } bits;
    };


    /**
     * @brief 按键输入滤波，并触发点击、长按等事件，默认输入低电平有效
     *
     * @tparam LONG_PRESS_THRESHOLD 多个扫描周期中持续按下，触发长按
     * @tparam TRIGGER_COUNT        默认为2，表示按键电平必须持续稳定低电平2 个周期，才会触发按键状态切换
     * @tparam BufferType           用作bit 缓冲区的整数类型，默认至少8bit
     */
    template <uint8_t LONG_PRESS_THRESHOLD = 10, uint8_t TRIGGER_COUNT = 2, typename BufferType = uint_fast8_t>
    class ButtonTrigger {
       public:
        using BitBufferType = BufferType;

        static constexpr uint8_t TRIGGER_LEN = TRIGGER_COUNT + 1;
        static_assert(TRIGGER_LEN < (sizeof(BufferType) * 8));

        static constexpr BufferType FULL_MASK = ~static_cast<BufferType>(0);
        static constexpr auto TRIGGER_MASK = static_cast<BufferType>(FULL_MASK << (TRIGGER_LEN));
        static constexpr auto PRE_MASK = static_cast<BufferType>(1 << TRIGGER_COUNT);

       private:
        BufferType _scan_buffer = 0;

        uint_fast8_t _press_counter = 0;

        // 内部状态
        bool _pressed = false;

        // 按键事件标志，使用者读取事件后自动清零
        EventFlag _flag;

        /**
         * @brief 检测缓冲区的状态是否达到按键按下的消抖阈值
         *
         * 如果滤波设定是连续2 个低电平有效，则只有当_scan_buffer 的第2 位为1，0, 1 两位都为0 才视为有效，
         * 也就是说，不光要检测到连续低电平，还要在低电平之前有至少一个高电平。
         *
         * @return true   按键已按下
         * @return false  按键状态不稳定或已抬起
         */
        bool is_pressed() const {
            auto pre = _scan_buffer ^ PRE_MASK;
            return (pre | TRIGGER_MASK) == TRIGGER_MASK;
        }


        /**
         * @brief 检测缓冲区的状态是否达到按键抬起的消抖阈值
         *
         * @return true   按键已抬起
         * @return false  按键状态不稳定或已按下
         */
        bool is_released() const {
            auto pre = _scan_buffer ^ PRE_MASK;
            return (pre | TRIGGER_MASK) == FULL_MASK;
        }


       public:
        /**
         * @brief 返回button_pressed 标志并自动清零
         *
         * @return true
         * @return false
         */
        bool pressed() {
            bool v = _flag.bits.pressed;
            _flag.bits.pressed = 0;
            return v;
        }


        /**
         * @brief 返回button_released 标志并自动清零
         *
         * @return true
         * @return false
         */
        bool released() {
            bool v = _flag.bits.released;
            _flag.bits.released = 0;
            return v;
        }


        /**
         * @brief 返回button_clicked 标志并自动清零
         *
         * @return true
         * @return false
         */
        bool clicked() {
            bool v = _flag.bits.clicked;
            _flag.bits.clicked = 0;
            return v;
        }


        /**
         * @brief 返回button_long_pressed 标志并自动清零
         *
         * @return true
         * @return false
         */
        bool long_pressed() {
            bool v = _flag.bits.long_pressed;
            _flag.bits.long_pressed = 0;
            return v;
        }


        /**
         * @brief 返回button_long_press_released 标志并自动清零
         *
         * @return true
         * @return false
         */
        bool long_press_released() {
            bool v = _flag.bits.long_press_released;
            _flag.bits.long_press_released = 0;
            return v;
        }


        /**
         * @brief 按键是否正被按下
         *
         * @return true
         * @return false
         */
        bool pressing() const {
            return _pressed;
        }


        /**
         * @brief 输入按键电平信号，过滤抖动，触发按键事件。低电平有效。
         *
         * @param level
         */
        EventFlag feed(bool level) {
            _scan_buffer <<= 1;
            if (level) {
                _scan_buffer += 1;
            }

            if (!_pressed) {
                if (is_pressed()) {  // 连续多次低电平，表示按键确实按下了
                    _pressed = 1;
                    _flag.bits.pressed = 1;  // 函数外面的标志位由主程序检查，使用一次后清零，以免重复触发
                }
            }
            else {
                if (is_released()) {  // 连续多次高电平，表示按键抬起。
                    _pressed = 0;

                    // 如果按下的时间没到长按的阈值，则在按键抬起时发出clicked 信号，
                    // 否则发出长按抬起信号
                    if (_press_counter < LONG_PRESS_THRESHOLD) {
                        _flag.bits.clicked = 1;
                    }
                    else {
                        _flag.bits.long_press_released = 1;
                    }

                    _press_counter = 0;
                    _flag.bits.released = 1;
                }
                else {
                    // press_counter 在发出long_pressed 信号后就要停止自增，不然press_counter 溢出后又重新从0 开始自增，long_pressed 信号会重复发出
                    if (_press_counter == LONG_PRESS_THRESHOLD) {
                        _flag.bits.long_pressed = 1;
                    }

                    if (_press_counter > LONG_PRESS_THRESHOLD) {
                        // 如果press_counter 大于阈值，则停止自增，此时press_counter 的值是BUTTON_LONG_PRESS_THRESHOLD + 1，
                        // 不会重复给long_pressed 置位，但是相应的，BUTTON_LONG_PRESS_THRESHOLD + 1 最大值为255，阈值就不能超过254
                    }
                    else {
                        ++_press_counter;
                    }
                }
            }

            return _flag;
        }


        /**
         * @brief 输入按键电平信号，过滤抖动，触发按键事件。低电平有效。
         *
         * @param level
         */
        EventFlag feed_negative(bool level) {
            return feed(level);
        }


        /**
         * @brief 输入按键电平信号，过滤抖动，触发按键事件。高电平有效。
         *
         * @param level
         */
        EventFlag feed_positive(bool level) {
            return feed(!level);
        }


        /**
         * @brief 完全复位按键滤波器
         *
         * 相比只复位按键事件，完全复位后需要一段时间才能恢复正常工作循环。
         * 一般情况只复位按键事件就足以处理按键冲突等问题。
         *
         */
        void reset() {
            _pressed = false;
            _press_counter = 0;
            _scan_buffer = 0;
            reset_event();
        }


        void reset_event() {
            _flag.flag = 0;
        }


        /**
         * @brief 获取当前缓冲区的值（用于测试）
         */
        BufferType get_bit_buffer() const {
            return _scan_buffer;
        }


        /**
         * @brief 获取按压计数器值（用于测试）
         */
        uint_fast8_t get_press_counter() const {
            return _press_counter;
        }
    };

}  // namespace button_trigger